<template>
  <span>
    {{ displayValue }}
  </span>
</template>

<script>
// 此组件参考vue-count-to实现
import { computed, onMounted, reactive, toRef, unref, watch } from 'vue'

export default {
  name: 'FCountTo',
  props: {
    startVal: {
      type: Number,
      default: 0,
    },
    endVal: {
      type: Number,
      default: 2000,
    },
    duration: {
      type: Number,
      default: 2000,
    },
    autoplay: {
      type: Boolean,
      default: true,
    },
    decimals: {
      type: Number,
      required: false,
      default: 0,
      validator(value) {
        return value >= 0
      },
    },
    decimal: {
      type: String,
      default: '.',
    },
    separator: {
      type: String,
      default: ',',
    },
    prefix: {
      type: String,
      default: '',
    },
    suffix: {
      type: String,
      default: '',
    },
    useEasing: {
      type: Boolean,
      default: true,
    },
    easingFn: {
      type: Function,
      default(t, b, c, d) {
        return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
      },
    },
  },
  emits: ['mounted', 'callback'],
  setup(props, { emit }) {
    const state = reactive({
      localStartVal: props.startVal,
      displayValue: formatNumber(props.startVal),
      printVal: null,
      paused: false,
      localDuration: props.duration,
      startTime: null,
      timestamp: null,
      remaining: null,
      rAF: null,
    })
    onMounted(() => {
      if (props.autoplay) {
        start()
      }
      emit('mounted')
    })

    const getCountDown = computed(() => props.startVal > props.endVal)
    watch([() => props.startVal, () => props.endVal], () => {
      if (props.autoplay) {
        start()
      }
    })

    function start() {
      const { startVal, duration } = props
      state.localStartVal = startVal
      state.startTime = null
      state.localDuration = duration
      state.paused = false
      state.rAF = requestAnimationFrame(count)
    }

    function pauseResume() {
      if (state.paused) {
        resume()
        state.paused = false
      } else {
        pause()
        state.paused = true
      }
    }

    function pause() {
      cancelAnimationFrame(state.rAF)
    }

    function resume() {
      state.startTime = null
      state.localDuration = +state.remaining
      state.localStartVal = +state.printVal
      requestAnimationFrame(count)
    }

    function reset() {
      state.startTime = null
      cancelAnimationFrame(state.rAF)
      state.displayValue = formatNumber(props.startVal)
    }

    function count(timestamp) {
      const { useEasing, easingFn, endVal } = props
      if (!state.startTime) state.startTime = timestamp
      state.timestamp = timestamp
      const progress = timestamp - state.startTime
      state.remaining = state.localDuration - progress
      if (useEasing) {
        if (unref(getCountDown)) {
          state.printVal = state.localStartVal - easingFn(progress, 0, state.localStartVal - endVal, state.localDuration)
        } else {
          state.printVal = easingFn(
            progress,
            state.localStartVal,
            endVal - state.localStartVal,
            state.localDuration,
          )
        }
      } else {
        if (unref(getCountDown)) {
          state.printVal = state.localStartVal - (state.localStartVal - endVal) * (progress / state.localDuration)
        } else {
          state.printVal = state.localStartVal + (endVal - state.localStartVal) * (progress / state.localDuration)
        }
      }
      if (unref(getCountDown)) {
        state.printVal = state.printVal < endVal ? endVal : state.printVal
      } else {
        state.printVal = state.printVal > endVal ? endVal : state.printVal
      }
      state.displayValue = formatNumber(state.printVal)
      if (progress < state.localDuration) {
        state.rAF = requestAnimationFrame(count)
      } else {
        emit('callback')
      }
    }

    function formatNumber(num) {
      const { decimals, decimal, separator, suffix, prefix } = props
      num = Number(num).toFixed(decimals)
      num += ''
      const x = num.split('.')
      let x1 = x[0]
      const x2 = x.length > 1 ? decimal + x[1] : ''
      const rgx = /(\d+)(\d{3})/
      if (separator) {
        while (rgx.test(x1)) {
          x1 = x1.replace(rgx, '$1' + separator + '$2')
        }
      }
      return prefix + x1 + x2 + suffix
    }

    function restart() {
      reset()
      start()
    }

    return {
      count,
      reset,
      resume,
      start,
      pauseResume,
      restart,
      displayValue: toRef(state, 'displayValue'),
    }
  },
}
</script>
